/*
 * txdnet.cn tonydon
 * by netbeans ide 7.2
 */
package cn.txdnet.txweb.basedao;

import java.lang.reflect.Method;
import java.util.Set;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.CallbackFilter;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import net.sf.cglib.proxy.NoOp;
import txdnet.util.Logger;
import txdnet.util.MethodUtil;

/**
 * dao facade 代理类
 *
 * @author tangxiaodong
 */
public class FacadeProxy {
    
    private static Class clazz = FacadeProxy.class;

    /**
     *
     * 获取 daoClazz 的自动提交事务代理对象。 其中，{@link SessionMgr} 由 daoClazz 的默认构造函数提供。
     *
     * @param daoClazz	: 被代理的数据库访问类对象
     * @return	与 daoClazz 有相同接口的代理对象
     *
     */
    public static final <F extends AbstractFacade<M, S>, M extends SqlSessionMgr<S>, S> F getAutoCommitProxy(Class<F> daoClazz) {
        return getProxy(daoClazz, true);
    }

    /**
     *
     * 获取 daoClazz 的自动提交事务代理对象。 其中，{@link SessionMgr} 由 mgr 指定。
     *
     * @param daoClazz	: 被代理的数据库访问类对象
     * @param mgr	: {@link SessionMgr}
     * @return	与 daoClazz 有相同接口的代理对象
     *
     */
    public static final <F extends AbstractFacade<M, S>, M extends SqlSessionMgr<S>, S> F getAutoCommitProxy(Class<F> daoClazz, M mgr) {
        return getProxy(daoClazz, mgr, true);
    }

    /**
     *
     * 获取 daoClazz 的手动提交事务代理对象。 其中，{@link SessionMgr} 由 daoClazz 的默认构造函数提供。
     *
     * @param daoClazz	: 被代理的数据库访问类对象
     * @return	与 daoClazz 有相同接口的代理对象
     *
     */
    public static final <F extends AbstractFacade<M, S>, M extends SqlSessionMgr<S>, S> F getManualCommitProxy(Class<F> daoClazz) {
        return getProxy(daoClazz, false);
    }

    /**
     *
     * 获取 daoClazz 的手动提交事务代理对象。 其中，{@link SessionMgr} 由 mgr 指定。
     *
     * @param daoClazz	: 被代理的数据库访问类对象
     * @param mgr	: {@link SessionMgr}
     * @return	与 daoClazz 有相同接口的代理对象
     *
     */
    public static final <F extends AbstractFacade<M, S>, M extends SqlSessionMgr<S>, S> F getManualCommitProxy(Class<F> daoClazz, M mgr) {
        return getProxy(daoClazz, mgr, false);
    }

    /**
     *
     * 获取 daoClazz 的手动提交事务代理对象。 其中，{@link SessionMgr} 由 mgr 指定。
     *
     * @param daoClazz	: 被代理的数据库访问类对象
     * @param level	: {@link TransIsoLevel}
     * @return	与 daoClazz 有相同接口的代理对象
     *
     */
    public static final <F extends AbstractFacade<M, S>, M extends SqlSessionMgr<S>, S> F getManualCommitProxy(Class<F> daoClazz, TransIsoLevel level) {
        return getProxy(daoClazz, null, false, level);
    }

    /**
     *
     * 获取 daoClazz 的手动提交事务代理对象。 其中，{@link SessionMgr} 由 mgr 指定。
     *
     * @param daoClazz	: 被代理的数据库访问类对象
     * @param mgr	: {@link SessionMgr}
     * @param level	: {@link TransIsoLevel}
     * @return	与 daoClazz 有相同接口的代理对象
     *
     */
    public static final <F extends AbstractFacade<M, S>, M extends SqlSessionMgr<S>, S> F getManualCommitProxy(Class<F> daoClazz, M mgr, TransIsoLevel level) {
        return getProxy(daoClazz, mgr, false, level);
    }

    /**
     *
     * 获取 daoClazz 的代理对象。 其中，{@link SessionMgr} 由 daoClazz 的默认构造函数提供，是否自动提交事务
     * transactional 指定。
     *
     * @param daoClazz	: 被代理的数据库访问类对象
     * @param autoCommit	: 是否自动提交事务（为了提高执行效率，对于只执行查询的访问通常采用自动提交事务）
     * @return	与 daoClazz 有相同接口的代理对象
     *
     */
    public static final <F extends AbstractFacade<M, S>, M extends SqlSessionMgr<S>, S> F getProxy(Class<F> daoClazz, boolean autoCommit) {
        return getProxy(daoClazz, null, autoCommit);
    }

    /**
     *
     * 获取 daoClazz 的代理对象。 其中，{@link SessionMgr} 由 mgr 指定，是否自动提交事务由 autoCommit
     * 参数指定。
     *
     * @param daoClazz	: 被代理的数据库访问类对象
     * @param mgr	: {@link SessionMgr}
     * @param autoCommit	: 是否自动提交事务（为了提高执行效率，对于只执行查询的访问通常采用自动提交事务）
     * @return	与 daoClazz 有相同接口的代理对象
     *
     */
    public static final <F extends AbstractFacade<M, S>, M extends SqlSessionMgr<S>, S> F getProxy(Class<F> daoClazz, M mgr, boolean autoCommit) {
        return getProxy(daoClazz, mgr, autoCommit, TransIsoLevel.DEFAULT);
    }

    /**
     *
     * 获取 daoClazz 的代理对象。 其中，{@link SessionMgr} 由 mgr 指定，是否自动提交事务由 autoCommit
     * 参数指定。
     *
     * @param daoClazz	: 被代理的数据库访问类对象
     * @param mgr	: {@link SessionMgr}
     * @param autoCommit	: 是否自动提交事务（为了提高执行效率，对于只执行查询的访问通常采用自动提交事务）
     * @param level	: {@link TransIsoLevel}
     * @return	与 daoClazz 有相同接口的代理对象
     *
     */
    public static final <F extends AbstractFacade<M, S>, M extends SqlSessionMgr<S>, S> F getProxy(Class<F> daoClazz, M mgr, boolean autoCommit, TransIsoLevel level) {
        Class<?>[] argTypes = mgr != null ? new Class<?>[]{mgr.getClass()} : new Class<?>[]{};
        Object[] args = mgr != null ? new Object[]{mgr} : new Object[]{};
        Callback intercepter = new Interceptor<F, M, S>(autoCommit, level);

        return getProxy(daoClazz, argTypes, args, intercepter);
    }

    @SuppressWarnings("unchecked")
    private static final <F extends AbstractFacade<M, S>, M extends SqlSessionMgr<S>, S> F getProxy(Class<F> daoClazz, Class<?>[] argTypes, Object[] args, Callback intercepter) {
        Enhancer en = new Enhancer();

        en.setSuperclass(daoClazz);
        en.setCallbackFilter(InterceptFilter.INSTANCE);
        Callback[] callbacks = {NoOp.INSTANCE, intercepter}; // 因先调用了过滤器，所以第一个拦截器使用CGLIB的默认拦截实例，没有做任何事
        en.setCallbacks(callbacks);
        en.setInterceptDuringConstruction(false);

        return (F) en.create(argTypes, args);
    }

    /**
     * 函数回调过滤器
     */
    private static class InterceptFilter implements CallbackFilter {

        private static final InterceptFilter INSTANCE = new InterceptFilter();
        private static final Set<Method> FILTER_METHODS = MethodUtil.getAllMethods(AbstractFacade.class);

        @Override
        public final int accept(Method method) {
            if (FILTER_METHODS.contains(method)) {
                return 0; // 针对AbstractFacade的方法都不做任何拦截 对应于0位的NoOp.INSTANCE
            }
            return 1;
        }
    }

    /**
     * 事务回调拦截器
     */
    private static class Interceptor<F extends AbstractFacade<M, S>, M extends SqlSessionMgr<S>, S> implements MethodInterceptor {

        private boolean autoCommit;
        private TransIsoLevel transLevel;

        Interceptor(boolean autoCommit, TransIsoLevel transLevel) {
            this.autoCommit = autoCommit;
            this.transLevel = transLevel;
        }

        @Override
        @SuppressWarnings("unchecked")
        public final Object intercept(Object dao, Method method, Object[] args, MethodProxy proxy) throws Throwable {
            Object result = null;
            F facade = (F) dao;

            if (facade.isInvoking()) {
                result = proxy.invokeSuper(dao, args);
                
            } else {
                facade.setInvoking(true);

                M manager = facade.getSessionMgr();
                if(manager == null){
                    throw new RuntimeException("SqlSessionMgr Object Is Null.");
                }
                
                TransIsoLevel defLevel = manager.getDefaultTransIsoLevel();
                boolean alterTransLevel = (!autoCommit && transLevel != TransIsoLevel.DEFAULT && transLevel != defLevel);

                try {
                    // 是否使用代理事务级别替换SqlSessionMgr中的事务级别
                    if (alterTransLevel) {
                        manager.setSessionTransIsoLevel(transLevel);
                    }
                    if (!autoCommit) {
                        manager.beginTrans();
                    }
                    result = proxy.invokeSuper(dao, args);
                    if (!autoCommit) {
                        manager.commit();
                    }
                } catch (Exception e) {
                    if (!autoCommit) {
                        try {
                            manager.rollback();
                        } catch (Exception ex) {
                            Logger.error(clazz, ex);
                        }
                    }
                    throw new RuntimeException(e);
                } finally {
                    if (alterTransLevel) {
                        try {
                            manager.setSessionTransIsoLevel(defLevel);
                        } catch (Exception ex) {
                            Logger.error(clazz, ex);
                        }
                    }
                    try {
                        manager.closeSession();
                    } catch (Exception ex) {
                        Logger.error(clazz, ex);
                    }

                    facade.setInvoking(false);
                }
            }
            return result;
        }
    }
}
